﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace Windows.Toolkit.Controls
{
    public class DecoratedListBoxDragDropTarget : ItemsControlDragDropTarget<ListBox, ListBoxItem>
    {
        #region Properties

        /// <summary>
        /// Gets the ListBox that is the drag drop target.
        /// </summary>
        /// <value>The ListBox that is the drag drop target.</value>
        protected ListBox ListBox
        {
            get { return Content as ListBox; }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="DecoratedListBoxDragDropTarget"/> class.
        /// </summary>
        public DecoratedListBoxDragDropTarget()
        {
            this.DefaultStyleKey = typeof(DecoratedListBoxDragDropTarget);
        }

        #endregion

        #region Methods

        /// <summary>
        /// Adds all selected items when drag operation begins.
        /// </summary>
        /// <param name="eventArgs">Information about the event.</param>
        protected override void OnItemDragStarting(ItemDragEventArgs eventArgs)
        {
            SelectionCollection selectionCollection = new SelectionCollection();

            // If panel is virtualized there is no way of knowing the precise
            // index of each selected item.
            Panel itemsHost = this.ListBox.GetItemsHost();
            if (itemsHost is VirtualizingPanel)
            {
                foreach (object item in this.ListBox.SelectedItems)
                {
                    selectionCollection.Add(new Selection(item));
                }

                // Adding the item dragged even if it isn't selected
                SelectionCollection defaultSelectionCollection = ToSelectionCollection(eventArgs.Data);

                if (defaultSelectionCollection.Count == 1 && !selectionCollection.Any(selection => object.Equals(selection.Item, defaultSelectionCollection[0].Item)))
                {
                    selectionCollection.Add(defaultSelectionCollection[0]);
                }
            }
            else
            {
                for (int cnt = 0; cnt < this.ListBox.Items.Count; cnt++)
                {
                    ListBoxItem listBoxItem = this.ListBox.ItemContainerGenerator.ContainerFromIndex(cnt) as ListBoxItem;
                    if (listBoxItem.IsSelected)
                    {
                        selectionCollection.Add(new Selection(cnt, this.ListBox.Items[cnt]));
                    }
                }

                // Adding the item dragged even if it isn't selected
                SelectionCollection defaultSelectionCollection = GetSelectionCollection(eventArgs.Data);
                if (defaultSelectionCollection.Count == 1)
                {
                    if (selectionCollection.All(selection => selection.Index != defaultSelectionCollection[0].Index))
                    {
                        selectionCollection.Add(defaultSelectionCollection[0]);
                    }
                }
            }

            eventArgs.Data = selectionCollection;

            Grid panel = new Grid();

            var itemContainers = from s in GetSelectedItems(selectionCollection)
                                 where s != null
                                 select ListBox.ItemContainerGenerator.ContainerFromItem(s);

            foreach (ListBoxItem row in itemContainers.OfType<UIElement>())
            {
                Image image = new Image();

                var decorator = GetDecorator(row);
                if (decorator != null)
                {
                    Visibility oldVisibility = decorator.Visibility;

                    decorator.Visibility = Visibility.Visible;
                    decorator.UpdateLayout();

                    image.Source = new WriteableBitmap(decorator, new TranslateTransform());

                    decorator.Visibility = oldVisibility;
                }
                else
                {
                    image.Source = new WriteableBitmap(row, new TranslateTransform());
                }

                panel.Children.Add(image);
            }

            eventArgs.DragDecoratorContent = panel;

            eventArgs.Handled = true;

            base.OnItemDragStarting(eventArgs);
        }

        private static SelectionCollection ToSelectionCollection(object data)
        {
            if (data == null)
            {
                return new SelectionCollection();
            }

            SelectionCollection selectionCollection = data as SelectionCollection;
            if (selectionCollection == null)
            {
                selectionCollection = new SelectionCollection();
                Selection selection = data as Selection;
                if (selection != null)
                {
                    selection = new Selection(data);
                }
                selectionCollection.Add(selection);
            }

            return selectionCollection;
        }

        private IEnumerable<object> GetSelectedItems(SelectionCollection s)
        {
            return s.Select<Selection, object>(delegate(Selection selection)
            {
                return selection.Item;
            });
        }

        private static SelectionCollection GetSelectionCollection(object data)
        {
            ItemDragEventArgs args = data as ItemDragEventArgs;

            if (args != null)
            {
                data = args.Data;
            }

            return ToSelectionCollection(data);
        }

        private static DragSource GetDecorator(DependencyObject target)
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(target); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(target, i);

                if (child != null)
                {
                    if (child.GetType() == typeof(DragSource))
                    {
                        return (DragSource)child;
                    }
                    else
                    {
                        var decorator = GetDecorator(child);
                        if (decorator != null)
                        {
                            return decorator;
                        }
                    }
                }
            }

            return null;
        }


        /// <summary>
        /// Ensures the content of control is a ListBox.
        /// </summary>
        /// <param name="oldContent">The old content.</param>
        /// <param name="newContent">The new content.</param>
        protected override void OnContentChanged(object oldContent, object newContent)
        {
            if (newContent != null && !(newContent is ListBox))
            {
                throw new ArgumentException("Content must be a ListBox.");
            }

            base.OnContentChanged(oldContent, newContent);
        }

        #endregion
    }
}
